#include "ModulePlayer.h"

namespace modx {

	ModulePlayer::ModulePlayer() {
		m_pMixer = NULL;
		m_pModule = NULL;
		channel = NULL;
		stop();
	}
	ModulePlayer::~ModulePlayer() {
		delete channel;
	}
	
	bool ModulePlayer::useMixer(ModuleMixer* mixerToUse) {
		m_pMixer = mixerToUse;
		if(mixerToUse) {
			sndVars.mixFreqPeriod = ((AMIGA_CONST<<9) / (m_pMixer->getSampleRate())) << 3;
			return true;
		}
		
		return false;
	}
	
	bool ModulePlayer::useModule(Module* moduleToUse) {
		m_pModule = moduleToUse;
		if(moduleToUse) {
			stop();
			
			if(channel) delete channel;
			channel = new MOD_CHANNEL[m_pModule->moduleChannels];
			return true;
		}
		
		return false;
	}
	
	void ModulePlayer::play() {
		if(m_pModule && m_pMixer) {
			m_pMixer->setNumChannels(m_pModule->moduleChannels);
			orderCount	= m_pModule->songLength;
			curOrder	= 0;

			tick		= 6;
			speed		= DEFAULT_SPEED;
			patDelay = 0;
			//rowPtr		= m_pModule->modulePatterns[0].getRow(0);
			seek(0, 0);
			curRow	= -1;
			state		= PLAY_STATE_PLAY;

			setTempo(DEFAULT_TEMPO);
			sndVars.samplesUntilMODTick = 0;	
			
			state = PLAY_STATE_PLAY;
		} else {
			stop();
		}
	}
	void ModulePlayer::stop() {
		state = PLAY_STATE_STOP;
	}
	void ModulePlayer::pause() {
	
	}
	
	unsigned long ModulePlayer::render(short out[], unsigned long nSamples) {
		long samplesLeft = nSamples;
		long samplesMixed = 0;

		while(samplesLeft > 0)
		{
			if(state != PLAY_STATE_PLAY) {
				return samplesMixed;
			}
				// Check if the song needs updated
			if(sndVars.samplesUntilMODTick < (1<<RESAMP_SHIFT) && state == PLAY_STATE_PLAY)
			{
					// Update the song and set the number of samples to mix before the next update
				update();
				sndVars.samplesUntilMODTick += sndVars.samplesPerMODTick;
			}

				// Figure out if this is the last batch of samples for this frame
			if((sndVars.samplesUntilMODTick>>RESAMP_SHIFT) < samplesLeft && state == PLAY_STATE_PLAY)
			{
					// Song will need updated before we're out of samples, so mix up to the song tick
				m_pMixer->out16(out+(samplesMixed), sndVars.samplesUntilMODTick>>RESAMP_SHIFT);
					// Subtract the number we just mixed
				samplesLeft -= sndVars.samplesUntilMODTick>>RESAMP_SHIFT;
				samplesMixed += sndVars.samplesUntilMODTick>>RESAMP_SHIFT;

					// No more left, so song will get updated next time through the loop
				sndVars.samplesUntilMODTick -= (sndVars.samplesUntilMODTick>>RESAMP_SHIFT)<<RESAMP_SHIFT;
			}
			else
			{
					// Either song is not playing, so just mix a full buffer, or 
					// not enough samples left to make it to another song tick,
					// so mix what's left and exit
				//_SndMix(samplesLeft);
				m_pMixer->out16(out+(samplesMixed), samplesLeft);
				samplesMixed += samplesLeft;
					// This is how many samples will get mixed first thing next frame
					// before updating the song
				sndVars.samplesUntilMODTick -= samplesLeft<<RESAMP_SHIFT;
					// Mixed the last of the 304 samples, this will break from the while loop
				samplesLeft = 0;
			}
			
		}
		
		return nSamples;
	}
	
	inline unsigned short ModulePlayer::getSampleNotePeriod(unsigned char note, unsigned char sample) {
		unsigned long sampleTune = m_pModule->moduleSamples[sample].sampleTuneFrequency;
		return 8363 * moduleNotes[note] / sampleTune;
	}
	
	bool ModulePlayer::seek(unsigned long order, unsigned long row) {
		curOrder = order;
		if(curOrder >= orderCount)
		{
			stop();			// Hit the end of the song, so stop it
			return false;		// FALSE = song ended
		}
			
		curRow = row;
		if(curRow >= PATTERN_ROWS)
			curRow = 0;	// Row was too high, just set it to 0

		nextOrder = curOrder + 1;
		breakRow  = -1;
		rowPtr = m_pModule->modulePatterns[m_pModule->moduleOrders[curOrder]].getRow(curRow);

		return true;			// TRUE = continue playing		
	}
	
	void ModulePlayer::update() {
		if(state != PLAY_STATE_PLAY)
			return;

		tick++;
		if(tick >= speed)
		{
			tick = 0;
			curRow++;
			
			if(patDelay == 0) {
				if(curRow >= PATTERN_ROWS)
				{
					if(seek(nextOrder, breakRow) == false)
						return;		// FALSE = song ended
				}
				rowPtr = m_pModule->modulePatterns[m_pModule->moduleOrders[curOrder]].getRow(curRow);
				processRow();
			} else {
				patDelay--;
			}
		}
		else
		{
			updateEffects();
		}	
	}
	
	void ModulePlayer::processRow() {
		long curChannel;
		for(curChannel = 0; curChannel < m_pModule->moduleChannels; curChannel++)
		{

				// Quick initialization, with values for row-tick
			UPDATE_VARS vars = defaultVars[EFFECT_TABLE_ROW];

			vars.modChn = &channel[curChannel];
			vars.sndChn = m_pMixer->getChannel(curChannel);

				// Read in the pattern data, advancing rowPtr to the next channel in the process
			vars.note   = rowPtr->at(curChannel).noteNumber;
			vars.sample = rowPtr->at(curChannel).sampleNumber;
			vars.effect = rowPtr->at(curChannel).effectNumber;
			vars.param  = rowPtr->at(curChannel).effectParam;

				// Set these for the mid-ticks
			
			vars.modChn->effect = vars.effect;
			vars.modChn->param  = vars.param;

				// Set sample and volume BEFORE effect processing, because some 
				// effects read the sample from the MOD channel rather than vars, 
				// and others need to override the default volume
			if(vars.sample != NO_SAMPLE)   // Never set local to memory anymore (explained below)
			{
					// Set sample memory
				if(vars.note != NO_NOTE) 
					vars.modChn->note = vars.note;
				vars.modChn->sample			= vars.sample;
				vars.modChn->vol			= m_pModule->moduleSamples[vars.sample].sampleVolume;
					// Don't set mixer channel volume until after effect processing
					//vars.sndChn->vol          = vars.modChn->vol;
				vars.updateFlags			|= UPD_FLG_SET_VOL;
			}

				// Effect 0 is arpeggio, but is also used as no-effect if the param is 0 too
			if(vars.effect != 0 || vars.param != 0) {
				rowEffects(&vars);
			}

			handleUpdateFlags(&vars);
		}	
	}
	
	void ModulePlayer::updateEffects() {
		long curChannel;
		for(curChannel = 0; curChannel < m_pModule->moduleChannels; curChannel++)
		{
				// Bail if there's no effect to update
			if( channel[curChannel].effect != 0 || 
				channel[curChannel].param  != 0 )
			{
					// Initialize with mid-tick values now
				UPDATE_VARS vars = defaultVars[EFFECT_TABLE_MID];

				vars.modChn = &channel[curChannel];
				vars.sndChn = m_pMixer->getChannel(curChannel);

				// Already made sure there was an effect
				tickEffects(&vars);

				handleUpdateFlags(&vars);
			}
		}	
	}
	
	void ModulePlayer::handleUpdateFlags(UPDATE_VARS *vars) {
		if((vars->note != NO_NOTE) && 
		   (vars->updateFlags & UPD_FLG_PLAY_NOTE) )
			playNote(vars);

		if(vars->updateFlags & UPD_FLG_SET_VOL)
			vars->sndChn->vol = vars->modChn->vol;

			// mixFreqPeroid is already shifted up 12 bits for fixed-point
		if(vars->updateFlags & UPD_FLG_SET_FREQ)
			vars->sndChn->inc = sndVars.mixFreqPeriod / vars->modChn->period;
	}
	
	void ModulePlayer::playNote(UPDATE_VARS *vars) {
		const ModuleSample *sample;

			// Here's that special case that they didn't specify a sample before playing a note
		if(vars->modChn->sample == NO_SAMPLE)
		{
			return;
		}
			// Handy
		sample = &m_pModule->moduleSamples[vars->modChn->sample];


		vars->modChn->period  = getSampleNotePeriod(vars->note, vars->modChn->sample);

			// Set up the mixer channel
		vars->sndChn->data       = sample->sampleData;
		vars->sndChn->pos        = vars->sampleOffset<<8<<RESAMP_SHIFT;
		vars->updateFlags 		|= UPD_FLG_SET_FREQ;
		vars->sndChn->length	 = sample->sampleLength << RESAMP_SHIFT;
		vars->sndChn->loopLength = sample->sampleLoopLength << RESAMP_SHIFT;
		vars->sndChn->loopStart = sample->sampleLoopStart << RESAMP_SHIFT;
		
	}
	
	unsigned long ModulePlayer::volSlide(unsigned long volume, long slide) {
		volume += slide;
		if(volume > 64) {
			if(slide > 0)
				volume = 64;
			else
				volume = 0;
		}
		return volume;
	}
	
	long ModulePlayer::pitchSlide(long period, long slide) {
		period += slide;
		if(period > PERIOD_MAX)
			period = PERIOD_MAX;
		else if(period < PERIOD_MIN)
			period = PERIOD_MIN;
			
		return period;
	}
	
	void ModulePlayer::volSlideRow(UPDATE_VARS *vars) {
		if((vars->param>>4) >0 && (vars->param&0xf) > 0) return;
	
		if((vars->param>>4) > 0) {
			vars->modChn->volslideSpeed = (vars->param>>4);
		} else if((vars->param&0xf) > 0) {
			vars->modChn->volslideSpeed = -(vars->param&0xf);
		}
	}
	
	void ModulePlayer::volSlideTick(UPDATE_VARS *vars) {
		vars->modChn->vol = volSlide(vars->modChn->vol, vars->modChn->volslideSpeed);
		vars->updateFlags |= UPD_FLG_SET_VOL;	
	}
	
	void ModulePlayer::tonePortaRow(UPDATE_VARS *vars) {
		if(vars->note != NO_NOTE)
			vars->modChn->tonePortaNote = vars->note;
			
		if(vars->param)
			vars->modChn->tonePortaSpeed = vars->param;
				
		vars->updateFlags &= ~UPD_FLG_PLAY_NOTE;
	}
	
	void ModulePlayer::tonePortaTick(UPDATE_VARS *vars) {
		unsigned short targetPeriod = getSampleNotePeriod(vars->modChn->tonePortaNote, vars->modChn->sample);
		
		if(vars->modChn->period < targetPeriod) {
			vars->modChn->period = pitchSlide(vars->modChn->period, vars->modChn->tonePortaSpeed);
			if(vars->modChn->period > targetPeriod)
				vars->modChn->period = targetPeriod;
		} else if(vars->modChn->period > targetPeriod) {
			vars->modChn->period = pitchSlide(vars->modChn->period, -(int)vars->modChn->tonePortaSpeed);
			if(vars->modChn->period < targetPeriod)
				vars->modChn->period = targetPeriod;
		}
		
		vars->updateFlags |= UPD_FLG_SET_FREQ;	
	}
	
	void ModulePlayer::rowEffects(UPDATE_VARS *vars) {
		unsigned char effect = vars->effect;
		unsigned char param = vars->param;	
		unsigned char paramx = param >> 4;
		unsigned char paramy = param & 0xF;
		
		switch(effect) {
		
			case 0x0:
				if(vars->modChn->note == NO_NOTE) {
					vars->modChn->effect = vars->modChn->param = 0;
				}
			break;
			
			case 0x1:
			case 0x2:
				if(param) {
					vars->modChn->portaSpeed = param;
				}
			break;
			
			case 0x3:
				tonePortaRow(vars);
			break;
			
			case 0x9:
				vars->sampleOffset = vars->param;
			break;
			
			case 0xA:
				volSlideRow(vars);
			break;
			
			case 0xB:
				nextOrder = param;
				curRow = PATTERN_ROWS;
			break;
		
			case 0xC:
				vars->modChn->vol = param;
				vars->updateFlags |= UPD_FLG_SET_VOL;
			break;
			
			case 0xD:
				breakRow = param;
				curRow = PATTERN_ROWS;
			break;
		
			case 0xF:
				if(param <= 32) {
					if(param > 1) {
						speed = param;
					}
				}
				if(param >= 33) {
					setTempo(param);
				}
			break;
			
			default:
			break;
		}
	}
	
	void ModulePlayer::tickEffects(UPDATE_VARS *vars) {
		unsigned char effect = vars->modChn->effect;
		unsigned char param = vars->modChn->param;	
		unsigned char paramx = param >> 4;
		unsigned char paramy = param & 0xF;
		unsigned long arpNote;
		
		switch(effect) {
		
			case 0x0:
				switch(tick % 3) {
					default:
					case 0: arpNote = vars->modChn->note; break;
					case 1: arpNote = vars->modChn->note+paramx; break;
					case 2: arpNote = vars->modChn->note+paramy; break;
				}
				
				if(arpNote > MAX_NOTES) {
					arpNote = MAX_NOTES;
				}
				
				vars->modChn->period = 8363 * moduleNotes[arpNote] / m_pModule->moduleSamples[vars->modChn->sample].sampleTuneFrequency;
				vars->updateFlags |= UPD_FLG_SET_FREQ;
			break;
			
			case 0x1:
				vars->modChn->period = pitchSlide(vars->modChn->period, -vars->modChn->portaSpeed);
				vars->updateFlags |= UPD_FLG_SET_FREQ;
			break;
			
			case 0x2:
				vars->modChn->period = pitchSlide(vars->modChn->period, vars->modChn->portaSpeed);
				vars->updateFlags |= UPD_FLG_SET_FREQ;				
			break;
			
			case 0x3:
				tonePortaTick(vars);
			break;
		
			case 0xA: 
				volSlideTick(vars);
			break;
			
			default:
			break;
		}
	}
	
	void ModulePlayer::setTempo(long lTempo) {
		tempo = lTempo;

		sndVars.samplesUntilMODTick	-= sndVars.samplesPerMODTick;
		sndVars.samplesPerMODTick	 = (m_pMixer->getSampleRate()*5<<RESAMP_SHIFT) / (tempo*2);
		sndVars.samplesUntilMODTick	+= sndVars.samplesPerMODTick;
	}
}